#pragma once

#include <iostream>
#include <string>
#include <mutex>
#include <vector>
#include <cassert>
#include <fstream>
#include <json/json.h>

#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "../comm/httplib.h"
#include "oj_model_mysql.hpp"
#include "oj_view.hpp"

namespace ns_control
{
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    //提供主机服务
    class Machine
    {
    public:
        std::string _ip;//编译服务主机ip
        int _port;//编译服务主机端口
        uint16_t _load;//编译服务主机负载
        std::mutex *_mtx;//互斥锁
    public:
        Machine():_ip(""),_port(0),_load(0),_mtx(nullptr){}
        ~Machine(){}

    public:
        //增加主机负载
        void Incload()
        {
            if(_mtx)
                _mtx->lock();
            ++_load;
            if(_mtx)
                _mtx->unlock();
        }

        //减少主机负载
        void Decload()
        {
            if(_mtx)
                _mtx->lock();
            --_load;
            if(_mtx)
                _mtx->unlock();
        }

        //重置主机负载
        void Resetload()
        {
            if(_mtx)
                _mtx->lock();
            _load=0;
            if(_mtx)
                _mtx->unlock();
        }

        //获取主机负载
        uint16_t Getload()
        {
            uint16_t load_value=0;
            if(_mtx)
                _mtx->lock();
            load_value=_load;
            if(_mtx)
                _mtx->unlock();
            return load_value;
        }
    };

    //提供服务的主机列表
    const std::string service_machines="./conf/service_machines.conf";

    //负载均衡模块
    class LoadBalance
    {
    private:
        std::vector<Machine> _machines;//主机列表
        std::vector<int> _online_machines;//在线主机列表
        std::vector<int> _offline_machines;//离线主机列表
        std::mutex _mtx;//保证LoadBalance的数据一致性

    public:
        LoadBalance()
        {
            assert(LoadConf(service_machines));
            LOG(INFO)<<"主机["<<service_machines<<"]加载成功\n";
        }
        ~LoadBalance(){}
    public:
        //加载主机配置文件
        bool LoadConf(const std::string &machine_conf)
        {
            //打开配置文件
            std::ifstream in(machine_conf);
            if(!in.is_open())
            {
                LOG(FATAL)<<"打开主机配置文件["<<machine_conf<<"]失败\n";
                return false;
            }
            //按行读取文件
            std::string line;
            while(std::getline(in,line))
            {
                //解析主机配置文件
                std::vector<std::string> tokens;
                //提取IP和端口
                StringUtil::SplitString(line,&tokens,":");
                if(tokens.size()!=2)
                {
                    LOG(FATAL)<<"切分["<<line<<"]失败\n";
                    continue;
                }
                //创建主机对象
                Machine machine;
                machine._ip=tokens[0];
                machine._port=atoi(tokens[1].c_str());
                machine._load=0;
                machine._mtx=new std::mutex();
                //将主机对象加入到负载均衡模块的主机列表和在线主机的ID列表中
                _online_machines.push_back(_machines.size());
                _machines.push_back(machine);
            }
            in.close();
            return true;
        }

        //负载均衡选择
        bool SelectMachine(int *id,Machine **machine)
        {
            _mtx.lock();

            int online_num=_online_machines.size();
            if(online_num==0)
            {
                _mtx.unlock();
                LOG(FATAL)<<"没有在线主机\n";
                return false;
            }
            //选择负载最低的主机
            *id=_online_machines[0];
            *machine=&_machines[0];
            uint64_t min_load=_machines[_online_machines[0]].Getload();
            for(int i=1;i<online_num;i++)
            {
                uint64_t load=_machines[_online_machines[i]].Getload();
                if(load<min_load)
                {
                    min_load=load;
                    *id=_online_machines[i];
                    *machine=&_machines[_online_machines[i]];
                }
            }
            _mtx.unlock();
            return true;
        }

        //主机上下线管理
        void OnlineMachine()
        {
            _mtx.lock();
            _online_machines.insert(_online_machines.end(),_offline_machines.begin(),_offline_machines.end());
            _offline_machines.erase(_offline_machines.begin(),_offline_machines.end());
            _mtx.unlock();

            LOG(INFO)<<"所有主机已上线\n";
        }
        void OfflineMachine(int id)
        {
            _mtx.lock();
            //从在线列表中找到要离线的主机
            for(auto it=_online_machines.begin();it!=_online_machines.end();it++)
            {
                if(*it==id)
                {
                    _machines[id].Resetload();//重置负载
                    _online_machines.erase(it);//从在线列表中删除
                    _offline_machines.push_back(id);//加入到离线列表
                    break;
                }
            }
            _mtx.unlock();
        }

        //for test
        void ShowMachine()
        {
            _mtx.lock();
            std::cout<<"在线主机列表：\n";
            for(auto &id:_online_machines)
            {
                std::cout<<id<<" ";
            }
            std::cout<<std::endl;
            std::cout<<"离线主机列表：\n";
            for(auto &id:_offline_machines)
            {
                std::cout<<id<<" ";
            }
            std::cout<<std::endl;
            _mtx.unlock();
        }
    };
    class Control
    {
        //用于从后台数据获取题目相关信息
        Model _model;
        //用于将题目信息展示给用户
        View _view;
        //用于负载均衡判题
        LoadBalance _load_balance;

    public:
        //获取所有题目的信息，并生成一个显示所有题目的网页
        bool AllProblems(std::string *html)
        {
            bool ret =true;
            std::vector<struct Problem> problems;
            if(this->_model.GetAllProblems(&problems))
            {
                //根据题号排序
                sort(problems.begin(),problems.end(),[](const struct Problem &p1,const struct Problem &p2)
                {
                    return atoi(p1.id.c_str())<atoi(p2.id.c_str());
                });
                //获取题目信息成功，将所有题目数据构建成网页
                _view.AllExpandHtml(problems,html);
            }
            else
            {
                *html="获取网页失败，获取题目列表失败";
                ret=false;
            }
            return ret;
        }

        //获取指定题目的信息，并生成一个显示指定题目的网页
        bool Problem(const std::string &id,std::string *html)
        {
            bool ret=true;
            struct Problem problem;
            if(_model.GetOneProblem(id,&problem))
            {
                _view.OneExpandHtml(problem,html);
            }
            else
            {
                *html="指定题目，编号："+id+"不存在";
                ret=false;
            }
            return ret;
        }

        //负载均衡判题
        //参数：
        //id:题目编号
        //in_json:用户提交的代码和输入
        //返回值
        //out_json:判题结果
        void Judge(const std::string &id,const std::string in_json,std::string *out_json)
        {
            //获取题目信息
            struct Problem problem;
            _model.GetOneProblem(id,&problem);
            //反序列化：将用户提交的代码和输入反序列化成json对象
            Json::Reader reader;//创建Json读取器
            Json::Value in_value;//创建Json对象以储存输入
            reader.parse(in_json,in_value);//将输入解析成Json对象
            std::string code=in_value["code"].asString();//获取用户提交的代码

            //重新拼接用户代码和题目测试代码
            Json::Value compile_value;      //创建Json值对象用于编译
            compile_value["input"]=in_value["input"].asString();//获取用户输入
            compile_value["code"]=code+"\n"+problem.tail;//拼接用户代码和测试代码
            compile_value["cpu_limit"]=problem.cpu_limit;//获取题目cpu限制
            compile_value["mem_limit"]=problem.mem_limit;//获取题目内存限制
            Json::FastWriter writer;//创建Json写入器
            std::string compile_string=writer.write(compile_value);//将Json值对象写入字符串

            //选择负载最低的主机
            while(true)
            {
                int machine_id=0;//主机编号
                Machine *machine=nullptr;//主机指针
                if(!_load_balance.SelectMachine(&machine_id,&machine))
                {
                    break;//没有主机可用，跳出循环
                }
                //主机可用，向主机发送编译请求
                Client client(machine->_ip,machine->_port);//创建客户端对象
                machine->Incload();//主机负载加1

                LOG(INFO)<<"选择主机成功:["<<machine_id<<"]["<<machine->_ip<<":"<<machine->_port<<"],当前负载"<<machine->Getload()<<"\n";

                int resp_stauts=0;
                //发送编译请求
                if(auto res=client.Post("/compile_and_run",compile_string,"application/json;charset=UTF-8"))
                {
                    if(resp_stauts=res->status==200)//编译请求成功
                    {
                        *out_json=res->body;//将编译结果写入输出
                        machine->Decload();//主机负载减1
                        LOG(INFO)<<"请求编译与运行服务成功\n";
                        break;//跳出循环
                    }
                    else
                    {
                        //编译请求失败，向主机发送请求失败
                        machine->Decload();//主机负载减1
                    }
                }
                else//编译请求失败
                {
                    LOG(ERROR)<<"状态码："<<resp_stauts<<"无法连接到主机["<<machine->_ip<<":"<<machine->_port<<"]可能离线\n";

                    _load_balance.OfflineMachine(machine_id);//主机离线
                    _load_balance.ShowMachine();//显示主机负载
                }
            }
        }
        //当所有主机离线则重新上线
        void ReonlineMachine()
        {
            _load_balance.OnlineMachine();
        }
    };
}